<?php

/**
 * @version			$Id: hmysqli.php 1964 2012-07-05 10:28:02Z admin $
 * @create 			2012-3-7 15:02:11 By xjiujiu
 * @package 		hongjuzi.database.db
 * @subpackage 		mysql
 * @copyRight 		Copyright (c) 2011-2012 http://www.xjiujiu.com.All right reserved
 * HongJuZi Framework
 */
defined('HJZ_DIR') or die();

//定义Mysql的查询结果类型
define('ASSOC', PDO::FETCH_ASSOC);
define('NUM', PDO::FETCH_NUM);
define('BOTH', PDO::FETCH_BOTH);
define('OBJ', PDO::FETCH_OBJ);
//引入SQL生成工具
HClass::import('hongjuzi.database.db.mysql.HMysqlBase');
HClass::import('hongjuzi.scheme.mysql.HPdoSql');

/**
 * 以pdo方式实现对Mysql数据库的驱动
 * 
 * 使用了pdo里一些特有的特性来实现相关数据库的操作，灵活性更大
 * 
 * @author 			xjiujiu <xjiujiu@foxmail.com>
 * @package 		hongjuzi.database.db.mysql
 * @since 			1.0.0
 */
class HPdo extends HMysqlBase
{

    /**
     * @var static HMysqli $_db 数据库PDO的驱动对象
     */
    protected static $_db   = null;

    /**
     * @var PDO $_pdo
     */
    protected $_pdo;

    /**
     * @var HPdo_Stmt $_stmt 对象
     */
    protected $_stmt;

    /**
     * 构造函数 
     * 
     * @access public
     */
	public function __construct($hConfigs)
    {
        parent::__construct($hConfigs);
        $this->_pdo             = null;
        $this->_stmt            = null;
        $this->_transactions    = 0;
        $this->_initDb();
    }

    /**
     * 克隆方法 
     * 
     * @access public
     */
    public function __clone() {}

    /**
     * 得到唯一pdo对象的入口
     * 
     * 单例模式实现者 
     * 
     * @access public static
     * @return HPdo
     */
    public static function getInstance($hConfigs)
    {
        if(!(self::$_db instanceof HPdo)) {
            self::$_db  = new HPdo($hConfigs);
        }

        return self::$_db;
    }

    /**
     * 初始化数据库连接及选择对应的数据库 
     * 
     * @access protected
     */
    protected function _initDb()
    {
        $this->_connect();
        $this->_query('set names ' . $this->_hConfigs->dbCharset);
    }

    /**
     * {@inheritDoc} 
     */
    protected function _connect()
    {
        try{
            $dsn    = $this->_hConfigs->dbType
            . ':host=' . $this->_hConfigs->dbHost 
            . ';port=' . $this->_hConfigs->dbPort
            . ';dbname=' . $this->_hConfigs->dbName;
            if(empty($this->_hConfigs->options)){
                $this->_hConfigs->options = array();
            }
            $this->_hConfigs->options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES \'' . $this->_hConfigs->dbCharset . '\'';
            $this->_pdo     = new PDO(
                $dsn,
                $this->_hConfigs->dbUserName,
                $this->_hConfigs->dbUserPassword,
                $this->_hConfigs->options
            );
            $this->_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        }catch (Exception $ex){
            throw new HSqlException('数据库连接失败！' . $ex->getMessage());
        }
    }

    /**
     * {@inheritDoc} 
     */
    public function select($sql = '')
    {
        $sql    = empty($sql) ? $this->_hSql->getSelectSql() : $sql;
        $this->_getResult($sql);

        return $this;
    }

    /**
     * 得到查询结果 
     * 
     * @param string $sql执行的Sql语句 
     * @access protected
     */
    protected function _getResult($sql)
    {
        $this->_result  = $this->_query($sql);
    }

    /**
     * 得到单条记录
     * 
     * @access public
     */
    public function _getFetch($fetchMode = ASSOC)
    {
        $fetchMode  = empty($fetchMode) ? $this->_fetchMode : $fetchMode;
        switch($fetchMode) {
            case ASSOC:
                return $this->_result->fetch(PDO::FETCH_ASSOC);
            case OBJ:
                return $this->_result->fetch(PDO::FETCH_OBJ);
            case NUM:
                return $this->_result->fetch(PDO::FETCH_NUM);
            case BOTH:
            default:
                return $this->_result->fetch(PDO::FETCH_BOTH);
        }
    }

    /**
     * 释放记录结果集资源 
     * 
     * 当结果资源不为空时
     * 
     * @access protected
     */
    protected function _freeResult()
    {
        if(!empty($this->_result)) {
            $this->_result = null;
        }
    }

    /**
     * {@inheritDoc} 
     */
    public function add($sql = '')
    {
        $sql    = empty($sql) ? $this->_hSql->getAddSql() : $sql;
        $result = $this->_exec($sql);
        if(false !== $result) {
            return $this->getLastInsertId();
        }

        return false;
    }

    /**
     * {@inheritDoc} 
     */
    public function update($sql = '')
    {
        $sql    = empty($sql) ? $this->_hSql->getUpdateSql() : $sql;
        
        return $this->_exec($sql);
    }

    /**
     * {@inheritDoc} 
     */
    public function delete($sql = '')
    {
        $sql    = empty($sql) ? $this->_hSql->getDeleteSql() : $sql;

        return $this->_exec($sql);
    }

    /**
     * 对HMysqliSql里的是否为绑定变量执行形式的委托 
     * 
     * 这里得考虑一下是不是没有生成HSql对象的情况 
     * 
     * @access protected
     */
    protected function _isBindParam()
    {
        if(empty($this->_hSql) ||
           (false === $this->_hSql->isBindParam())) {
            return false;
        }

        return true;
    }

    /**
     * 得到最后插入的记录ID
     * 
     * 注意得设置auto_increment字段 
     * 
     * @access public
     * @return int 
     */
    public function getLastInsertId()
    {
        return $this->_pdo->lastInsertId();
    }

    /**
     *  执行一条 SQL 语句，并返回受影响的行数
     * 不支持参数的绑定
     * @param $sql 需要执行的SQL语句
     * @param bool $prefix 是否需要替换前缀
     * @return int
     */
    protected function _exec($sql, $prefix = true)
    {
        $sql    = $prefix == true ? $this->_replaceTablePrefix($sql) : $sql;
        try {
            $this->_reconnectDb();
            if(true === HObject::GC('DEBUG')) {
                HLog::write('SQL exec info: ' . $sql, HLog::$L_INFO);
            }
            $result = $this->_pdo->prepare($sql);
            $result->execute();

            return $result;
        } catch(PDOException $ex) {
            $this->rollBack();
            throw new HSqlException('SQL exec fail: ' . $sql . "\r\nMessage: " . $ex->getMessage());
        }
    }

    /**
     * 使用Mysql传统方式来执行Sql的操作 
     * 
     * 不支持参数的绑定
     * 
     * @access protected
     * @param string $sql 需要执行的SQL语句
     * @param string $prefix 是否需要替换前缀
     * @return resource 
     */
    protected function _query ($sql, $prefix = true)
    {
        $sql    = $prefix == true ? $this->_replaceTablePrefix($sql) : $sql;
        try {
            $this->_reconnectDb();
            $query  = $this->_pdo->query($sql);
            if(true === HObject::GC('DEBUG')) {
                HLog::write('SQL exec info: ' . $sql, HLog::$L_INFO);
            }
            $query->execute();

            return $query;
        } catch(PDOException $ex) {
            $this->rollBack();
            throw new HSqlException('SQL exec fail: ' . $sql . "\r\nMessage: " . $ex->getMessage());
        }
    }

    /**
     * 重新检测Db的连接情况
     * 
     * @author xjiujiu <xjiujiu@foxmail.com>
     * @access private
     */
    private function _reconnectDb()
    {
        if($this->_pdo) {
            return;
        }
        $this->_pdo = null;
        $this->_initDb();
    }

    /**
     * 使用Mysql传统方式来执行Sql的操作 
     * 
     * 不支持参数的绑定
     * 
     * @access public 
     * @param string $sql 需要执行的SQL语句
     * @param string $prefix 是否需要替换前缀
     * @return resource 
     */
    public function query($sql, $prefix = true)
    {
        $this->_result  = $this->_query($sql);

        return $this->_result;
    }

    /**
     * 得到Sql准备对象
     * 
     * 以绑定参数的形式 
     * 
     * @access protected
     * @param string $sql
     */
    protected function _prepare($sql, $prefix = true)
    {
        $sql    = $prefix == true ? $this->_replaceTablePrefix($sql) : $sql;
        $this->_stmt   = $this->_pdo->prepare($sql);
        if(true === HObject::GC('DEBUG')) {
            HLog::write('SQL exec info: ' . $sql, HLog::$L_INFO);
        }
        if(false === $this->_stmt) {
            $this->_freeHSql();
            throw new HSqlException('SQL语句执行错误！' . $sql);
        }
        $rows     = $this->_hSql->getValues();
        if(!is_array($rows[0])) {
            $rows = array($rows);
        }
        $result     = $this->_stmt->execute($rows);
        if(!$result){
            HLog::write('SQL执行错误！' . $sql, HLog::$L_ERROR);
        }
        $this->_result = $this->_stmt;
        $this->_freeHSql();

        return $result;
    }

    /**
     * 翻译HSql对象 
     * 
     * 每用完一次HSql对象就得重新生成不然会使用上一次的对象 
     * 
     * @access protected
     */
    protected function _freeHSql()
    {
        $this->_hSql    = null;
    }

    /**
     * 得到pdo的SQL语句生成对象
     * 
     * 得到SQL生成对象 
     * 
     * @access public
     * @return HPdoSql
     */
    public function getSql()
    {
        $this->_hSql    = new HPdoSql();

        return $this->_hSql;
    }

    /**
     * 得到数据库错误信息 
     * 
     * 包括Sql执行错误及其它类型的错误信息 
     * 
     * @access protected
     * @return string 
     */
    protected function getDbError()
    {
        $errorInfo  = $this->_pdo->errorInfo();
        return 'Code: ' . $this->_pdo->errorCode() .
               'Message' . $errorInfo[2];
    }

    /**
     * {@inheritDoc} 
     */
    public function close()
    {
        if($this->_pdo instanceof HPdo) {
            $this->_pdo = null;
        }
    }

    /**
     * 开启事务
     */
    public function beginTransaction()
    {
        $this->_transactions ++;
        if($this->_pdo->inTransaction()) {
            return;
        }
        $this->_pdo->beginTransaction();
    }

    /**
     * 提交事务
     */
    public function commit()
    {
        HLog::write('事务层级：' . $this->_transactions);
        if($this->_pdo->inTransaction()) {
            $this->_pdo->commit();
        }
    }

    /**
     * 回滚事务
     */
    public function rollBack()
    {
        if($this->_pdo->inTransaction()) {
            $this->_pdo->rollBack();
        }
    }

}
?>
